#include "vfx_shatter.h"

#include "graphics/graphics_util.h"
#include "math/math_core.h"
#include "num/rand.h"

#include "util/mmgr.h"		    // Needs to come in early
#include "util/common_macros.h"		// macros
#include "util/log.h"		    // logging
#include "util/timer.h"		    // logging
#include <cassert>
#include <stdlib.h>

using namespace vfx;

void VFXShatterSprite::Render(Graphics::Vertex2D* VertA, unsigned int VertC)
{
	assert(VertA && (VertC > 0) && (0 == (VertC % 3)));

	Graphics* pG = GetGraphics();
	assert(pG && "Graphics not valid!!!");

	if ((m_Sprite.m_UV1.x > 0.0f) || (m_Sprite.m_UV1.y > 0.0f) || (m_Sprite.m_UV2.x < 1.0f) || (m_Sprite.m_UV2.y < 1.0f))
	{
		math::Vec2 UV1 = m_Sprite.m_UV1;
		math::Vec2 UV2 = m_Sprite.m_UV2;
		for (int i = 0; i < (int)VertC; i++)
		{
			Graphics::Vertex2D* pVert = &VertA[i];
			pVert->u = UV1.x + (pVert->u * (UV2.x - UV1.x));
			pVert->v = UV1.y + (pVert->v * (UV2.y - UV1.y));
		}
	}

	m_Sprite.SetTexture();
	if (m_Sprite.m_Additive)
		pG->SetupAdditiveAlpha(true);

	pG->DrawVertex2D(VertA, VertC);

	if (m_Sprite.m_Additive)
		pG->SetupAdditiveAlpha(false);
}



VFXShatterFXBase::VFXShatterFXBase(bool EnableFade) :
	m_pElement(NULL),
	m_ShardA(NULL),
	m_ShardC(0),
	m_Width(0),
	m_Height(0),
	m_Acceleration(0.0f, 1.0f),
	m_Color(0xFFFFFFFF),
	m_AliveC(0),
	m_FadeDelay(0.0f),
	m_InvFadeDelay(1.0f),
	m_EnableFade(EnableFade),
	m_IsInit(false),
	m_IsShattered(false),
	m_pShatterStartCB(NULL),
	m_pShatterUpdateCB(NULL),
	m_pvShatterStartData(NULL),
	m_pvShatterUpdateData(NULL)
{
}

VFXShatterFXBase::~VFXShatterFXBase()
{
	UTIL_SAFE_DELETE(m_pElement);
	UTIL_SAFE_DELETEA(m_ShardA);
}

bool VFXShatterFXBase::Setup(VFXShatterElement* pElement, unsigned int SegmentW, unsigned int SegmentH)
{
	assert(pElement && SegmentW && SegmentH);

	UTIL_SAFE_DELETE(m_pElement);
	m_pElement = pElement;
	m_Width = SegmentW;
	m_Height = SegmentH;
	m_AliveC = 0;
	m_IsShattered = false;
	m_IsInit = false;

	UTIL_SAFE_DELETEA(m_ShardA);
	m_ShardC = 0;

	m_IsInit = CreateShard(&m_ShardA);
	return m_IsInit;
}

bool VFXShatterFXBase::Setup(const std::string& Sprite, unsigned int SegmentW, unsigned int SegmentH)
{
	return Setup( new VFXShatterSprite(Sprite), SegmentW, SegmentH );
}

void VFXShatterFXBase::Shatter(const math::Vec2& SpeedMinMax,
						   const math::Vec2& LifeMinMax,
						   const math::Vec2& RotationMinMax,
						   const math::Vec2& CenterOffset)
{
	if (!m_ShardA) return;
	if (m_IsShattered) return;
	assert(m_pElement);


	math::Vec2 CenterPos = CenterOffset + (0.5f * m_pElement->GetSize());
	math::Vec2 Dir;
	float Speed;

	for (unsigned int i = 0; i < m_ShardC; i++)
	{
		SHARD_DATA* pShard = &m_ShardA[i];

		Dir = pShard->Pos - CenterPos;
		if (Dir.MagSqr() > 0.0f)
			Dir.Normalize();
		else
			Dir = math::Vec2(0.0f, -1.0f);

		Speed					= num::RAND32::Instance().RandFloat(SpeedMinMax.x, SpeedMinMax.y);
		pShard->Velocity		= Speed * Dir;

		pShard->RotSpeed		= num::RAND32::Instance().RandFloat(RotationMinMax.x, RotationMinMax.y);
		if (rand() % 2)			pShard->RotSpeed = -pShard->RotSpeed;
		
		pShard->Life.SetLimit(	num::RAND32::Instance().RandFloat(LifeMinMax.x, LifeMinMax.y) );
		pShard->Life.Reset();

		pShard->IsAlive = true;
		m_AliveC++;

	}

	if (m_pShatterStartCB)
	{
		// custom update?
		for (unsigned int i = 0; i < m_ShardC; i++)
		{
			SHARD_DATA* pShard = &m_ShardA[i];
			// custom start
			m_pShatterStartCB(pShard, i, this, m_pvShatterStartData);
		}

	}
	m_IsShattered = true;
}

void VFXShatterFXBase::Tick(float dt)
{
	if (!m_IsInit) return;
	if (m_pElement) m_pElement->Tick(dt);
	if (m_ShardA && (m_AliveC > 0))
	{
		for (unsigned int i = 0; i < m_ShardC; i++)
		{
			UpdateShard(&m_ShardA[i], dt);
		}
	}
}

void VFXShatterFXBase::Render(const math::Vec2& Offset, float Scale)
{
	if (m_pElement && m_ShardA && m_IsInit)
	{
		Graphics::Vertex2D* VertA = GetGraphics()->GetTempVert();
		unsigned int idx = 0;

		assert(VertA && "No temp vertex buffer!!!");

		for (unsigned int i = 0; i < m_ShardC; i++)
		{
			SHARD_DATA* pShard = &m_ShardA[i];
			if (!pShard->IsAlive) continue;

			RenderShard(pShard, Offset, Scale, CalcShardColor(pShard), VertA, idx);

			assert( (TrianglesPerShard() > 0) && "Invalid TrianglesPerShard, dumbass!!!" );
			if ((idx + (TrianglesPerShard() * 3)) >= Graphics::MAX_TEMP_VERT)
			{
				// oops, we're full
				m_pElement->Render(VertA, idx);
				idx = 0;
			}
		}

		if (idx > 0)
			m_pElement->Render(VertA, idx);
	}
}

void VFXShatterFXBase::SetFadeDelay(float f)
{
	m_FadeDelay = f;
	UTIL_CLAMP(m_FadeDelay, 0.0f, 0.995000f);
	m_InvFadeDelay = 1.0f / (1.0f - m_FadeDelay);
}

COLOR VFXShatterFXBase::CalcShardColor(SHARD_DATA* pShard)
{
	assert(pShard);
	if (!pShard->IsAlive) return 0;

	COLOR c = m_Color;
	if (m_EnableFade)
	{
		assert((m_FadeDelay >= 0.0f) && (m_FadeDelay < 1.0f));
		float progress = pShard->Life.GetProgress();
		if (progress > m_FadeDelay)
		{
			float alpha = (1.0f - progress + m_FadeDelay) / m_InvFadeDelay;
			c = ColorSetAf(c, alpha);
		}
	}

	return c;
}

void VFXShatterFXBase::UpdateShard(SHARD_DATA* pShard, float dt)
{
	if (!pShard->IsAlive) return;
	if (!m_IsShattered) return;

	// update life
	pShard->Life.Tick(dt);
	if (pShard->Life.IsReady())
	{
		pShard->IsAlive = false;
		m_AliveC--;
		return;
	}

	if (m_pShatterUpdateCB)
	{
		// custom update
		m_pShatterUpdateCB(pShard, dt, this, m_pvShatterUpdateData);
	}
	else
	{
		// update new position and rotation
		pShard->Velocity += dt * m_Acceleration;
		pShard->Pos += dt * pShard->Velocity;
		pShard->Rotation = fmod(pShard->Rotation + (dt * pShard->RotSpeed), 360.0f);
	}
}


bool VFXShatterTriFX::CreateShard(SHARD_DATA** ppShardA)
{
	assert(m_pElement);

	m_ShardC = 2 * m_Width * m_Height;

	math::Vec2 ElemSize, SegmentSize, InvSize;
	SHARD_DATA* ShardA = new SHARD_DATA[m_ShardC];

	ElemSize		= m_pElement->GetSize();
	SegmentSize.x	= ElemSize.x / float(m_Width);
	SegmentSize.y	= ElemSize.y / float(m_Height);
	InvSize.x		= (ElemSize.x > 0.0f) ? (1.0f / ElemSize.x) : 1.0f;
	InvSize.y		= (ElemSize.y > 0.0f) ? (1.0f / ElemSize.y) : 1.0f;

	for (unsigned int x = 0; x < m_Width; x++)
	for (unsigned int y = 0; y < m_Height; y++)
	{
		unsigned int idx1 = 2 * (x + (y * m_Width));
		unsigned int idx2 = 1 + idx1;
		SHARD_DATA* pShard;

		assert(SHARD_DATA::MAX_POINTS >= 3);
		// TL shard
		//   0+-----+1
		//    |    /
		//    |   /
		//    |  /
		//    | /
		//   2+
		pShard = &ShardA[idx1];
		pShard->CustomID = TOPLEFT;
		pShard->PointC = 3;
		pShard->pvCustomData = NULL;
		pShard->IsAlive = true;
		pShard->Size = SegmentSize;
		pShard->Pos.x = ((0.5f + float(x)) * SegmentSize.x);
		pShard->Pos.y = ((0.5f + float(y)) * SegmentSize.y);
		pShard->UVA[0] = math::Vec2( float(x) * SegmentSize.x * InvSize.x, float(y) * SegmentSize.y * InvSize.y );
		pShard->UVA[1] = math::Vec2( float(x+1) * SegmentSize.x * InvSize.x, float(y) * SegmentSize.y * InvSize.y );
		pShard->UVA[2] = math::Vec2( float(x) * SegmentSize.x * InvSize.x, float(y+1) * SegmentSize.y * InvSize.y );
		pShard->Rotation = 0.0f;

		// BR shard
		//          +0
		//        / |
		//       /  |
		//      /   |
		//     /    |
		//   2+-----+1
		pShard = &ShardA[idx2];
		pShard->CustomID = BOTTOMRIGHT;
		pShard->PointC = 3;
		pShard->pvCustomData = NULL;
		pShard->IsAlive = true;
		pShard->Size = SegmentSize;
		pShard->Pos.x = ((0.5f + float(x)) * SegmentSize.x);
		pShard->Pos.y = ((0.5f + float(y)) * SegmentSize.y);
		pShard->UVA[0] = math::Vec2( float(x+1) * SegmentSize.x * InvSize.x, float(y) * SegmentSize.y * InvSize.y );
		pShard->UVA[1] = math::Vec2( float(x+1) * SegmentSize.x * InvSize.x, float(y+1) * SegmentSize.y * InvSize.y );
		pShard->UVA[2] = math::Vec2( float(x) * SegmentSize.x * InvSize.x, float(y+1) * SegmentSize.y * InvSize.y );
		pShard->Rotation = 0.0f;
	}

	*ppShardA = ShardA;
	return true;
}

void VFXShatterTriFX::RenderShard(SHARD_DATA* pShard, const math::Vec2& Offset, float Scale, COLOR c,
								  Graphics::Vertex2D* VertA, unsigned int& Idx)
{
	assert(pShard && pShard->IsAlive);

	// recalc point positions, inclusive of scaling
	const math::Vec2 HSize = Scale * 0.5f * pShard->Size;
	if (TOPLEFT == pShard->CustomID)
	{
		// TL shard
		//   0+-----+1
		//    |    /
		//    |   /
		//    |  /
		//    | /
		//   2+
		pShard->PointA[0]	= math::Vec2(pShard->Pos.x - HSize.x, pShard->Pos.y - HSize.y);
		pShard->PointA[1]	= math::Vec2(pShard->Pos.x + HSize.x, pShard->Pos.y - HSize.y);
		pShard->PointA[2]	= math::Vec2(pShard->Pos.x - HSize.x, pShard->Pos.y + HSize.y);
	}
	else
	{
		// BR shard
		//          +0
		//        / |
		//       /  |
		//      /   |
		//     /    |
		//   2+-----+1
		pShard->PointA[0]	= math::Vec2(pShard->Pos.x + HSize.x, pShard->Pos.y - HSize.y);
		pShard->PointA[1]	= math::Vec2(pShard->Pos.x + HSize.x, pShard->Pos.y + HSize.y);
		pShard->PointA[2]	= math::Vec2(pShard->Pos.x - HSize.x, pShard->Pos.y + HSize.y);
	}

	// rotation
	if (fabs(pShard->Rotation) > 0.000001f)
	{
		math::Vec2 pt;
		float angle;
		float rotc, rots;

		angle = fmod(pShard->Rotation - 90.0f, 360.0f);
		rotc = cosf(DEG_TO_RAD * angle);
		rots = sinf(DEG_TO_RAD * angle);

		for (int i = 0; i < 3; i++)
		{
			pt = pShard->PointA[i] - pShard->Pos;
			pShard->PointA[i].x = pShard->Pos.x + (rotc * pt.x) - (rots * pt.y);
			pShard->PointA[i].y = pShard->Pos.y + (rots * pt.x * GraphicsUtil::W2H) + (rotc * pt.y * GraphicsUtil::W2H);
		}
	}

	// fill buffer
	for (int i = 0; i < 3; i++)
	{
		VertA[Idx++].Fill( ((GetPos().x + pShard->PointA[i].x - Offset.x) * GraphicsUtil::WIDTH) - GraphicsUtil::PT_OFFSET,
						   ((GetPos().y + pShard->PointA[i].y - Offset.y) * GraphicsUtil::HEIGHT) - GraphicsUtil::PT_OFFSET,
						   c,
						   pShard->UVA[i].x,
						   pShard->UVA[i].y );
	}
}


bool VFXShatterRectFX::CreateShard(SHARD_DATA** ppShardA)
{
	assert(m_pElement);

	m_ShardC = m_Width * m_Height;

	math::Vec2 ElemSize, SegmentSize, InvSize;
	SHARD_DATA* ShardA = new SHARD_DATA[m_ShardC];

	ElemSize		= m_pElement->GetSize();
	SegmentSize.x	= ElemSize.x / float(m_Width);
	SegmentSize.y	= ElemSize.y / float(m_Height);
	InvSize.x		= (ElemSize.x > 0.0f) ? (1.0f / ElemSize.x) : 1.0f;
	InvSize.y		= (ElemSize.y > 0.0f) ? (1.0f / ElemSize.y) : 1.0f;

	for (unsigned int x = 0; x < m_Width; x++)
	for (unsigned int y = 0; y < m_Height; y++)
	{
		assert(SHARD_DATA::MAX_POINTS >= 4);
		//   0+------+1
		//    |    / |
		//    |   /  |
		//    |  /   |
		//    | /    |
		//   2+------+3

		unsigned int idx = x + (y * m_Width);
		SHARD_DATA* pShard = &ShardA[idx];
		pShard->PointC = 4;
		pShard->pvCustomData = NULL;
		pShard->IsAlive = true;
		pShard->Rotation = 0.0f;
		pShard->Size = SegmentSize;
		pShard->Pos.x = ((0.5f + float(x)) * SegmentSize.x);
		pShard->Pos.y = ((0.5f + float(y)) * SegmentSize.y);
		pShard->UVA[0] = math::Vec2( float(x) * SegmentSize.x * InvSize.x, float(y) * SegmentSize.y * InvSize.y );
		pShard->UVA[1] = math::Vec2( float(x+1) * SegmentSize.x * InvSize.x, float(y) * SegmentSize.y * InvSize.y );
		pShard->UVA[2] = math::Vec2( float(x) * SegmentSize.x * InvSize.x, float(y+1) * SegmentSize.y * InvSize.y );
		pShard->UVA[3] = math::Vec2( float(x+1) * SegmentSize.x * InvSize.x, float(y+1) * SegmentSize.y * InvSize.y );
	}

	*ppShardA = ShardA;
	return true;
}

void VFXShatterRectFX::RenderShard(SHARD_DATA* pShard, const math::Vec2& Offset, float Scale, COLOR c, Graphics::Vertex2D* VertA, unsigned int& Idx)
{
	assert(pShard && pShard->IsAlive);

	// recalc point positions, inclusive of scaling
	const math::Vec2 HSize = Scale * 0.5f * pShard->Size;

	//   0+------+1
	//    |    / |
	//    |   /  |
	//    |  /   |
	//    | /    |
	//   2+------+3
	pShard->PointA[0]	= math::Vec2(pShard->Pos.x - HSize.x, pShard->Pos.y - HSize.y);
	pShard->PointA[1]	= math::Vec2(pShard->Pos.x + HSize.x, pShard->Pos.y - HSize.y);
	pShard->PointA[2]	= math::Vec2(pShard->Pos.x - HSize.x, pShard->Pos.y + HSize.y);
	pShard->PointA[3]	= math::Vec2(pShard->Pos.x + HSize.x, pShard->Pos.y + HSize.y);

	// rotation
	if (fabs(pShard->Rotation) > 0.000001f)
	{
		math::Vec2 pt;
		float angle;
		float rotc, rots;

		angle = fmod(pShard->Rotation - 90.0f, 360.0f);
		rotc = cosf(DEG_TO_RAD * angle);
		rots = sinf(DEG_TO_RAD * angle);

		for (int i = 0; i < 4; i++)
		{
			pt = pShard->PointA[i] - pShard->Pos;
			pShard->PointA[i].x = pShard->Pos.x + (rotc * pt.x) - (rots * pt.y);
			pShard->PointA[i].y = pShard->Pos.y + (rots * pt.x * GraphicsUtil::W2H) + (rotc * pt.y * GraphicsUtil::W2H);
		}
	}

	// fill buffer 1
	for (int i = 0; i < 3; i++)
	{
		VertA[Idx++].Fill( ((GetPos().x + pShard->PointA[i].x - Offset.x) * GraphicsUtil::WIDTH) - GraphicsUtil::PT_OFFSET,
						   ((GetPos().y + pShard->PointA[i].y - Offset.y) * GraphicsUtil::HEIGHT) - GraphicsUtil::PT_OFFSET,
						   c,
						   pShard->UVA[i].x,
						   pShard->UVA[i].y );
	}

	// fill buffer 2
	for (int i = 0; i < 3; i++)
	{
		// 3-2-1 is easiest to formula, so use that
		int j = 3-i;
		VertA[Idx++].Fill( ((GetPos().x + pShard->PointA[j].x - Offset.x) * GraphicsUtil::WIDTH) - GraphicsUtil::PT_OFFSET,
						   ((GetPos().y + pShard->PointA[j].y - Offset.y) * GraphicsUtil::HEIGHT) - GraphicsUtil::PT_OFFSET,
						   c,
						   pShard->UVA[j].x,
						   pShard->UVA[j].y );
	}
}
